using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.ComponentModel;
using ViewOnCore.Helper.Collection;
using ViewOnCore.HostContract.Media;
using ViewOnCore.HostContract;

namespace ViewOnCore.Helper.WPF
{

    public partial class IntellisenseTextBox : System.Windows.Controls.UserControl
    {

        #region Variables

        private static string[] _noSuggestions = { "(no spelling suggestions)" };

        // Allow to lock focus
        private int _lockFocusNotification = 0;

        // Allow to lock text changed
        private int _lockTextChanged = 0;

        private IntellisenseManager _dbManager;
        private int _databaseIndex;

        #endregion

        #region Constructor

        public IntellisenseTextBox()
            : base()
        {
            InitializeComponent();

            //---- Text box
            textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
            textBox.LostFocus += new RoutedEventHandler(textBox_LostFocus);
            textBox.GotFocus += new RoutedEventHandler(textBox_GotFocus);

            //---- Suggestions list
            //lstSuggestions.IsKeyboardFocusWithinChanged += suggestionList_IsKeyboardFocusWithinChanged;
            lstSuggestions.MouseDoubleClick += suggestionList_MouseDoubleClick;
            lstSuggestions.PreviewKeyDown += suggestionList_PreviewKeyDown;
            lstSuggestions.LostFocus += new RoutedEventHandler(suggestionList_LostFocus);

            //---- Dialog's button
            if ((bool)GetValue(TextBox.IsReadOnlyProperty))
                btnTagsDialog.IsEnabled = false;
            btnTagsDialog.Click += new RoutedEventHandler(btnTagsDialog_Click);
        }

        #endregion

        #region InitializeIntellisense

        /// <summary>
        /// Connect the text box to the manager
        /// </summary>
        public void InitializeIntellisense(IntellisenseManager dbManager, int databaseIndex)
        {
            _dbManager = dbManager;
            _databaseIndex = databaseIndex;
        }

        #endregion

        #region Event : OnMouseDown

        /// <summary>
        /// Hides the list of suggestions.
        /// </summary>		
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);

            if (popup.IsOpen)
                HideSuggestions();
        }

        #endregion

        #region Event : OnKeyDown

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);

            // Show the suggestions
            if (!popup.IsOpen)
                AttemptToShowSuggestions();

            // Jump to the list box (if suggestions are available)
            if (e.Key == Key.Down && popup.IsOpen && Keyboard.FocusedElement != popup)
            {
                lstSuggestions.SelectedIndex = 0;

                ListBoxItem item = lstSuggestions.ItemContainerGenerator.ContainerFromIndex(lstSuggestions.SelectedIndex) as ListBoxItem;

                if (item != null)
                {
                    _lockFocusNotification++;
                    item.Focus();
                    _lockFocusNotification--;
                }
            }
        }

        /// <summary>
        /// Shows/hides the list of suggestions.
        /// </summary>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.Key == Key.Enter)
            {
                ApplySelectedSuggestion();
                return;
            }

            if (e.Key == Key.Escape)
            {
                AttemptToHideSuggestions();
                return;
            }
        }

        #endregion

        #region Event : TextChanged

        void textBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (_lockTextChanged > 0)
                return;

            AttemptToShowSuggestions();
        }

        #endregion

        #region Event : OnRenderSizeChanged

        /// <summary>
        /// Ensures that the list of suggestions is hidden when the TextBox is resized.
        /// </summary>
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);

            if (popup.IsOpen)
                HideSuggestions();
        }

        #endregion

        #region Focus management

        void textBox_GotFocus(object sender, RoutedEventArgs e)
        {
            if (_lockFocusNotification > 0 ||
                lstSuggestions.IsKeyboardFocused ||
                Keyboard.FocusedElement is ListBoxItem ||
                popup.IsOpen)
            {
                e.Handled = true;
                return;
            }
            
            this.OnGotFocus(new RoutedEventArgs(GotFocusEvent, this));
        }

        void textBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (_lockFocusNotification > 0 ||
                lstSuggestions.IsKeyboardFocused ||
                Keyboard.FocusedElement is ListBoxItem ||
                popup.IsOpen)
            {
                e.Handled = true;
                return;
            }

            this.OnLostFocus(new RoutedEventArgs(LostFocusEvent, this));
        }

        protected override void OnGotFocus(RoutedEventArgs e)
        {
            if (_lockFocusNotification > 0)
            {
                e.Handled = true;
                return;
            }

            base.OnGotFocus(e);
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            if (_lockFocusNotification > 0)
            {
                e.Handled = true;
                return;
            }

            base.OnLostFocus(e);
        }


        void suggestionList_LostFocus(object sender, RoutedEventArgs e)
        {
            if (this.IsKeyboardFocused)
                return;

            if (Keyboard.FocusedElement is ListBoxItem)
                return;

            // We have completely lost the focus
            this.OnLostFocus(e);
        }

        //void suggestionList_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
        //{
        //    // If the list of suggestions no longer contains the input focus
        //    // hide the list.
        //    bool focused = (bool)e.NewValue;
        //    if (!focused)
        //        HideSuggestions();
        //}

        #endregion

        #region Suggestion list : MouseDoubleClick

        void suggestionList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            // The user clicked on a suggestion, so apply it.
            ApplySelectedSuggestion();
        }

        #endregion

        #region Suggestion list : PreviewKeyDown

        void suggestionList_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (lstSuggestions.SelectedIndex < 0)
                return;

            //---- Escape : close the suggestions
            if (e.Key == Key.Escape)
            {
                HideSuggestions();
                e.Handled = true;
            }

            //---- Return to textbox and retreive one char
            else if (e.Key == Key.Back)
            {
                HideSuggestions();
                textBox.Focus();
                //textBox.OnKeyDown(e);
                e.Handled = true;
            }

            //---- Apply the suggestions
            else if (e.Key == Key.Space || e.Key == Key.Enter || e.Key == Key.Tab)
            {
                ApplySelectedSuggestion();
                e.Handled = true;
            }
        }

        #endregion

        #region ApplySelectedSuggestion

        void ApplySelectedSuggestion()
        {
            if (!popup.IsOpen || lstSuggestions.SelectedIndex < 0)
                return;

            // Correction
            string correctWord = (lstSuggestions.SelectedItem as IntellisenseListBoxItem).Content.ToString();

            // Indexes
            int startIndex = textBox.Text.LastIndexOfAny(new char[] { ',', ';' });
            if (startIndex < 0)
                startIndex = 0;
            textBox.CaretIndex = FindEndOfCurrentWord();

            // Replace
            string content;
            if (startIndex == 0)
                content = correctWord;
            else
                content = textBox.Text.Substring(0, startIndex) + ";" + correctWord;

            textBox.TextChanged -= new TextChangedEventHandler(textBox_TextChanged);
            textBox.Text = content;
            textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);

            // Reset
            HideSuggestions();
        }

        #endregion

        #region Properties

        public IntellisenseManager IntellisenseManager
        {
            get
            {
                return _dbManager;
            }
        }

        public int DatabaseIndex
        {
            get
            {
                return _databaseIndex;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return textBox.IsReadOnly;
            }
            set
            {
                textBox.IsReadOnly = value;
                btnTagsDialog.IsEnabled = !value;
            }
        }

        public string Text
        {
            get
            {
                return textBox.Text;
            }
            set
            {
                _lockTextChanged++;
                textBox.Text = value;
                _lockTextChanged--;
            }
        }

        #endregion

        #region SelectAll

        public void SelectAll()
        {
            _lockFocusNotification++;
            //textBox.SelectAll();
            _lockFocusNotification--;
        }

        #endregion

        #region AttemptToShowSuggestions

        private void AttemptToShowSuggestions()
        {
            //---- Prepare the list of suggestions
            List<IntellisenseListBoxItem> suggestions = GenerateSuggestions();

            // No suggestion
            if (suggestions == null || suggestions.Count < 1)
            {
                AttemptToHideSuggestions();
                return;
            }

            suggestions.Sort();

            //---- Set the new suggestions
            lstSuggestions.ItemsSource = suggestions;

            //---- Show the suggestions
            ShowSuggestions();
        }

        #endregion

        #region AttemptToHideSuggestions

        void AttemptToHideSuggestions()
        {
            // If there is not still a spelling error at the
            // caret location, hide the suggestions.
            if (popup.IsOpen)
                HideSuggestions();
        }

        #endregion

        #region HideSuggestions

        /// <summary>
        /// Hides the list of suggestions and returns input focus to the input area.  
        /// If the list of suggestions is not already displayed, nothing happens.
        /// </summary>
        private void HideSuggestions()
        {
            if (!popup.IsOpen)
                return;

            //---- Reset
            //lstSuggestions.ItemsSource = null;

            //---- Lock focus events
            _lockFocusNotification++;

            // Hide the pop up
            popup.IsOpen = false;

            //---- Set the focus to text box
            textBox.Focus();
            textBox.CaretIndex = textBox.Text.Length;

            //---- Unlock focus events
            _lockFocusNotification--;
        }

        #endregion

        #region ShowSuggestions

        /// <summary>
        /// Shows the list of suggestions.  If the current word is not misspelled
        /// this method does nothing.
        /// </summary>
        private void ShowSuggestions()
        {
            //---- Already visible
            if (popup.IsOpen)
                return;

            //---- Lock the focus events
            _lockFocusNotification++;

            //---- Display the list

            // Position the popup beneath the misspelled word.
            int idx = FindBeginningOfCurrentWord();
            Rect rect = textBox.GetRectFromCharacterIndex(idx);
            popup.HorizontalOffset = rect.Left;

            // Open the pop up
            popup.IsOpen = true;

            lstSuggestions.SelectedIndex = 0;

            //---- Unlock the focus events
            _lockFocusNotification--;
        }

        #endregion

        #region Helper : FindBeginningOfCurrentWord

        private int FindBeginningOfCurrentWord()
        {
            if (textBox.Text == null)
                return -1;

            int idx = textBox.CaretIndex;
            while (idx > 0)
            {
                char prevChar = textBox.Text[idx - 1];
                if (char.IsWhiteSpace(prevChar) || char.IsPunctuation(prevChar))
                    break;

                --idx;
            }
            return idx;
        }

        #endregion

        #region Helper : FindClosestCharacterInCurrentWord

        private int FindClosestCharacterInCurrentWord()
        {
            if (textBox.Text == null)
                return -1;

            int idx = textBox.CaretIndex;
            if (idx > 0)
            {
                char prevChar = textBox.Text[idx - 1];
                // If the caret is at the end of a word
                // then we have to use the preceding character
                // so that the typo will be found.
                if (!char.IsWhiteSpace(prevChar))
                    --idx;
            }
            return idx;
        }

        #endregion

        #region Helper : FindEndOfCurrentWord

        int FindEndOfCurrentWord()
        {
            if (textBox.Text == null)
                return -1;

            int targetIdx = textBox.CaretIndex;
            while (targetIdx < textBox.Text.Length)
            {
                char nextChar = textBox.Text[targetIdx];
                if (char.IsWhiteSpace(nextChar) || char.IsPunctuation(nextChar))
                    break;

                ++targetIdx;
            }
            return targetIdx;
        }

        #endregion

        #region GenerateSuggestions

        private List<IntellisenseListBoxItem> GenerateSuggestions()
        {
            //---- Get the last text
            string content = textBox.Text.Trim().ToLower();
            int index = content.LastIndexOfAny(new char[] { ',', ';' });
            if (index > -1)
                content = content.Substring(index + 1).Trim();

            if (content.Length < 1)
                return new List<IntellisenseListBoxItem>();

            //---- Set the values
            List<IntellisenseListBoxItem> values = new List<IntellisenseListBoxItem>();

            lock (_dbManager.DatabasesLock)
                foreach (string key in _dbManager.Databases[_databaseIndex].Keys)
                    if (key.StartsWith(content, StringComparison.OrdinalIgnoreCase))
                        if (!key.Equals(content, StringComparison.OrdinalIgnoreCase))
                            values.Add(new IntellisenseListBoxItem(key));

            return values;
        }

        #endregion

        #region Show Tags dialog

        void btnTagsDialog_Click(object sender, RoutedEventArgs e)
        {
            IntellisenseTagsDialog dialog = new IntellisenseTagsDialog(this);
            dialog.ShowDialog((Desktop)Host.ApplicationDesktop);
        }

        #endregion

        #region UpdateTextFromDialog

        internal void UpdateTextFromDialog(string text)
        {
            _lockTextChanged++;
            _lockFocusNotification++;
            textBox.Text = text;
            textBox.Focus();
            textBox.CaretIndex = text.Length;
            _lockFocusNotification--;
            _lockTextChanged--;

            OnLostFocus(new RoutedEventArgs(FocusManager.LostFocusEvent, this));
        }

        #endregion

    }

    #region IntellisenseListBoxItem

    sealed public class IntellisenseListBoxItem : ListBoxItem, IComparable
    {

        static IntellisenseListBoxItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(IntellisenseListBoxItem), new FrameworkPropertyMetadata(typeof(IntellisenseListBoxItem)));
        }

        public IntellisenseListBoxItem(object content):base()
        {
            this.Content = content;
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            //base.OnLostFocus(e);
        }

        protected override void OnGotFocus(RoutedEventArgs e)
        {
            //base.OnGotFocus(e);
        }

        #region IComparable Members

        int IComparable.CompareTo(object obj)
        {
            return ((IntellisenseListBoxItem)obj).Content.ToString().CompareTo(this.Content.ToString());
        }

        #endregion

    }

    #endregion

}